/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2008-2011, Salzburg Research. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * - Neither the name of the KiWi Project nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * Contributor(s):
 * sschaffe
 *
 * File Info:
 * User: sschaffe
 * Date: 2011/01/18
 * Time: 14:08
 *
 * Project: KiWi2, kiwi-core module
 */

package kiwi.core.services.triplestore;

import kiwi.core.api.persistence.PersistenceService;
import kiwi.core.api.transaction.TransactionService;
import kiwi.core.api.triplestore.LiteralService;
import kiwi.core.exception.transaction.TransactionNotActiveException;
import kiwi.core.model.Constants;
import kiwi.core.model.content.MediaContent;
import kiwi.core.model.content.TextContent;
import kiwi.core.model.rdf.*;
import kiwi.core.model.user.KiWiUser;
import kiwi.core.qualifiers.cache.KiWiCache;
import kiwi.core.qualifiers.user.CurrentUser;
import kiwi.core.util.KiWiFormatUtils;
import kiwi.core.util.MD5;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;
import org.slf4j.Logger;

import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import java.util.Date;
import java.util.Locale;

/**
 * Add file description here!
 * <p/>
 * User: sschaffe
 */
public class LiteralServiceImpl implements LiteralService {

    @Inject
    private Logger log;


    @Inject
    private TransactionService transactionService;

    @Inject
    private PersistenceService persistenceService;


    @Inject
    @KiWiCache("literal-cache")
    private Cache literalNodeCache;


    @Inject @CurrentUser
    private KiWiUser currentUser;



    /**
     * Create a new KiWiLiteral of the provided type.
     *
     * TODO: knowledge space support
     *
     * @param <T>
     * @param value
     * @param language
     * @param type
     * @return
     */
    public synchronized <T> KiWiLiteral createLiteral(T value, Locale language, String type) {
        if(value != null) {
            if(type == null) {
                type = getXSDType(value.getClass());
            }
            KiWiLiteral result = getLiteral(value, language, type);


            if(result == null) {
                String cacheKey = TripleStoreUtil.createLiteralCacheKey(MD5.md5sum(value.toString()),language,type);



                // create a new literal depending on type
                if(Date.class.equals(value.getClass())) {
                    result = new KiWiStringLiteral(KiWiFormatUtils.ISO8601FORMAT.format(value), language, type);
                } else if(Integer.class.equals(value.getClass()) || int.class.equals(value.getClass())) {
                    result = new KiWiIntLiteral(((Integer)value).longValue(), language, type);
                } else if(Long.class.equals(value.getClass()) || long.class.equals(value.getClass())) {
                    result = new KiWiIntLiteral((Long)value, language, type);
                } else if(Double.class.equals(value.getClass()) || double.class.equals(value.getClass())) {
                    result = new KiWiDoubleLiteral((Double)value, language, type);
                } else if(Float.class.equals(value.getClass()) || float.class.equals(value.getClass())) {
                    result = new KiWiDoubleLiteral(((Float)value).doubleValue(), language, type);
                } else {
                    result = new KiWiStringLiteral(value.toString(), language, type);
                }

                if(!transactionService.getTransaction().isActive()) {
                    log.error("cannot create literal {}, the transaction {} is not active",result,transactionService.getTransaction());
                    throw new TransactionNotActiveException("cannot create literal "+result+", the transaction "+transactionService.getTransaction()+" is not active");
                }


                transactionService.getTransaction().getData().getAddedNodes().add(result);
                literalNodeCache.put(new Element(cacheKey,result));


            }
            return result;
        } else {
            return null;
        }
    }


    /**
      * Create a new KiWiLiteral of the provided type.
      *
      * TODO: knowledge space support
      *
      * @param <T>
      * @param value
      * @param language
      * @param type
      * @return
      */
     public synchronized <T> KiWiLiteral getLiteral(T value, Locale language, String type) {
         if(value != null) {
             String cacheKey = TripleStoreUtil.createLiteralCacheKey(MD5.md5sum(value.toString()),language,type);

             KiWiLiteral result;


             if(literalNodeCache.get(cacheKey) != null) {
                 result = (KiWiLiteral) literalNodeCache.get(cacheKey).getObjectValue();

             } else {
                 String content_s;
                 content_s = value.toString();

                 EntityManager entityManager = persistenceService.getEntityManager();
                 EntityTransaction tx = entityManager.getTransaction();

                 tx.begin();

                 Query q;
                 if(language != null) {
                     q = entityManager.createNamedQuery("tripleStore.literalByValueLanguageType");
                     q.setParameter("lang", language);
                 } else {
                     q = entityManager.createNamedQuery("tripleStore.literalByValueType");
                 }
                 // TODO: I am not sure whether it is safe to use the MD5 value here, this could lead to
                 //       problems in exceptional cases ...
                 q.setParameter("md5", MD5.md5sum(content_s));
                 //q.setParameter("value", content_s);
                 q.setParameter("type", type);
                 q.setHint("org.hibernate.cacheable", true);
                 q.setMaxResults(1);

                 try {
                     result = (KiWiLiteral) q.getSingleResult();

                     literalNodeCache.put(new Element(cacheKey,result));
                 } catch(NoResultException ex) {
                     result = null;
                 }
                 tx.commit();
                 persistenceService.closeEntityManager(entityManager);

             }



             return result;
         } else {
             return null;
         }
     }



    /**
     * Create a new KiWiLiteral. The type is inferred by Java reflection.
     * @param <T>
     * @param value
     * @return
     */
    public <T> KiWiLiteral createLiteral(T value) {
    	return createLiteral(value, null, getXSDType(value.getClass()));
    }

    /**
     * Create a new KiWiLiteral. The type is inferred by Java reflection.
     * @param <T>
     * @param value
     * @return
     */
    public <T> KiWiLiteral getLiteral(T value) {
    	return getLiteral(value, null, getXSDType(value.getClass()));
    }


    /**
     * Create a new KiWiTextContentLiteral for representing KiWi textual content.
     */
    @Override
    public KiWiTextContentLiteral createTextContentLiteral(String content, Locale language) {
        if(content != null) {

            KiWiTextContentLiteral result = getTextContentLiteral(content,language);

            if(result == null) {
                result = new KiWiTextContentLiteral(content,language);
                result.setCreator(currentUser);

                String cacheKey = TripleStoreUtil.createLiteralCacheKey(MD5.md5sum(content),language, Constants.NS_KIWI_CORE+"TextContent");

                if(!transactionService.getTransaction().isActive()) {
                    log.error("cannot create literal {}, the transaction {} is not active",result,transactionService.getTransaction());
                    throw new TransactionNotActiveException("cannot create literal "+result+", the transaction "+transactionService.getTransaction()+" is not active");
                }

                transactionService.getTransaction().getData().getAddedNodes().add(result);
                literalNodeCache.put(new Element(cacheKey,result));
            }

            return result;
        } else {
            return null;
        }
    }

    /**
     * Create a new KiWiTextContentLiteral for representing KiWi textual content.
     */
    @Override
    public KiWiTextContentLiteral createTextContentLiteral(TextContent content) {
        if(content != null) {
            KiWiTextContentLiteral result = getTextContentLiteral(content);

            if(result == null) {
                String canonicalContent = content.getXmlString();
                Locale language         = content.getLang();

                String cacheKey = TripleStoreUtil.createLiteralCacheKey(MD5.md5sum(canonicalContent),language,Constants.NS_KIWI_CORE+"TextContent");

                result = new KiWiTextContentLiteral(currentUser);
                result.setTextContent(content);

                if(!transactionService.getTransaction().isActive()) {
                    log.error("cannot create literal {}, the transaction {} is not active",result,transactionService.getTransaction());
                    throw new TransactionNotActiveException("cannot create literal "+result+", the transaction "+transactionService.getTransaction()+" is not active");
                }

                transactionService.getTransaction().getData().getAddedNodes().add(result);
                literalNodeCache.put(new Element(cacheKey,result));
            }

            return result;
        } else {
            return null;
        }
    }


    /**
     * Create a new KiWiMediaContentLiteral for representing KiWi media content
     */
    @Override
    public KiWiMediaContentLiteral createMediaContentLiteral(MediaContent content) {
        if(content != null) {
            KiWiMediaContentLiteral result = getMediaContentLiteral(content);


            if(result == null) {
                String cacheKey = TripleStoreUtil.createLiteralCacheKey(MD5.md5sum(content.getData()),null,Constants.NS_KIWI_CORE+"MediaContent");

                result = new KiWiMediaContentLiteral(currentUser);
                result.setMediaContent(content);

                if(!transactionService.getTransaction().isActive()) {
                    log.error("cannot create literal {}, the transaction {} is not active",result,transactionService.getTransaction());
                    throw new TransactionNotActiveException("cannot create literal "+result+", the transaction "+transactionService.getTransaction()+" is not active");
                }

                transactionService.getTransaction().getData().getAddedNodes().add(result);

                literalNodeCache.put(new Element(cacheKey,result));
            }

            return result;
        } else {
            return null;
        }
    }



    /**
     * Create a new KiWiTextContentLiteral for representing KiWi textual content.
     */
    public KiWiTextContentLiteral getTextContentLiteral(String content, Locale language) {
        if(content != null) {
            String cacheKey = TripleStoreUtil.createLiteralCacheKey(MD5.md5sum(content),language,Constants.NS_KIWI_CORE+"TextContent");

            KiWiTextContentLiteral result;


            if(literalNodeCache.get(cacheKey) != null) {
                result = (KiWiTextContentLiteral) literalNodeCache.get(cacheKey).getObjectValue();

            } else {
                EntityManager entityManager = persistenceService.getEntityManager();
                EntityTransaction tx = entityManager.getTransaction();

                tx.begin();
                Query q;
                if(language != null) {
                    q = entityManager.createNamedQuery("tripleStore.literalByValueLanguageType");
                    q.setParameter("lang", language);
                } else {
                    q = entityManager.createNamedQuery("tripleStore.literalByValueType");
                }
                // TODO: I am not sure whether it is safe to use the MD5 value here, this could lead to
                //       problems in exceptional cases ...
                q.setParameter("md5", MD5.md5sum(content));
                //q.setParameter("value", content_s);
                q.setParameter("type", Constants.NS_KIWI_CORE+"TextContent");
                q.setHint("org.hibernate.cacheable", true);
                q.setMaxResults(1);

                try {
                    result = (KiWiTextContentLiteral) q.getSingleResult();
                    literalNodeCache.put(new Element(cacheKey,result));
                } catch(NoResultException ex) {
                    result = null;
                }
                tx.commit();
                persistenceService.closeEntityManager(entityManager);
            }

            return result;
        } else {
            return null;
        }
    }

    /**
      * Create a new KiWiTextContentLiteral for representing KiWi textual content.
      */
     public KiWiTextContentLiteral getTextContentLiteral(TextContent content) {
         if(content != null) {
             String canonicalContent = content.getXmlString();
             Locale language         = content.getLang();

             String cacheKey = TripleStoreUtil.createLiteralCacheKey(MD5.md5sum(canonicalContent),language,Constants.NS_KIWI_CORE+"TextContent");

             KiWiTextContentLiteral result;


             if(literalNodeCache.get(cacheKey) != null) {
                 result = (KiWiTextContentLiteral) literalNodeCache.get(cacheKey).getObjectValue();

             } else {
                 EntityManager entityManager = persistenceService.getEntityManager();
                 EntityTransaction tx = entityManager.getTransaction();

                 tx.begin();
                 Query q;
                 if(language != null) {
                     q = entityManager.createNamedQuery("tripleStore.literalByValueLanguageType");
                     q.setParameter("lang", language);
                 } else {
                     q = entityManager.createNamedQuery("tripleStore.literalByValueType");
                 }
                 // TODO: I am not sure whether it is safe to use the MD5 value here, this could lead to
                 //       problems in exceptional cases ...
                 q.setParameter("md5", MD5.md5sum(canonicalContent));
                 //q.setParameter("value", content_s);
                 q.setParameter("type", Constants.NS_KIWI_CORE+"TextContent");
                 q.setHint("org.hibernate.cacheable", true);
                 q.setMaxResults(1);

                 try {
                     result = (KiWiTextContentLiteral) q.getSingleResult();
                     literalNodeCache.put(new Element(cacheKey,result));
                 } catch(NoResultException ex) {
                     result = null;
                 }
                 tx.commit();
                 persistenceService.closeEntityManager(entityManager);
             }

             return result;
         } else {
             return null;
         }
     }


    /**
     * Create a new KiWiMediaContentLiteral for representing KiWi media content
     */
    public KiWiMediaContentLiteral getMediaContentLiteral(MediaContent content) {
        if(content != null) {
            String cacheKey = TripleStoreUtil.createLiteralCacheKey(MD5.md5sum(content.getData()),null,Constants.NS_KIWI_CORE+"MediaContent");

            KiWiMediaContentLiteral result;


            if(literalNodeCache.get(cacheKey) != null) {
                result = (KiWiMediaContentLiteral) literalNodeCache.get(cacheKey).getObjectValue();

            } else {
                EntityManager entityManager = persistenceService.getEntityManager();
                EntityTransaction tx = entityManager.getTransaction();

                tx.begin();
                Query q = entityManager.createNamedQuery("tripleStore.literalByValueType");
                q.setParameter("md5", MD5.md5sum(content.getData()));
                q.setParameter("type", Constants.NS_KIWI_CORE+"MediaContent");
                q.setHint("org.hibernate.cacheable", true);
                q.setMaxResults(1);

                try {
                    result = (KiWiMediaContentLiteral) q.getSingleResult();
                    literalNodeCache.put(new Element(cacheKey,result));
                } catch(NoResultException ex) {
                    result = null;
                }
                tx.commit();
                persistenceService.closeEntityManager(entityManager);
            }

            return result;
        } else {
            return null;
        }
    }






    /**
      * Return the appropriate XSD type for RDF literals for the provided Java class.
      * @param javaClass
      * @return
      */
     private String getXSDType(Class javaClass) {
         if(String.class.isAssignableFrom(javaClass)) {
             return Constants.NS_XSD+"string";
         } else if(Integer.class.isAssignableFrom(javaClass)) {
             return Constants.NS_XSD+"integer";
         } else if(Long.class.isAssignableFrom(javaClass)) {
             return Constants.NS_XSD+"long";
         } else if(Double.class.isAssignableFrom(javaClass)) {
             return Constants.NS_XSD+"double";
         } else if(Float.class.isAssignableFrom(javaClass)) {
             return Constants.NS_XSD+"float";
         } else if(Date.class.isAssignableFrom(javaClass)) {
             return Constants.NS_XSD+"dateTime";
         } else if(Boolean.class.isAssignableFrom(javaClass)) {
             return Constants.NS_XSD+"boolean";
         } else {
             return Constants.NS_XSD+"string";
         }
     }

    /**
     * Clear all caches that are currently active; used e.g. in case of a rollback to avoid
     * detached triples that are not really persisted.
     */
    public void clearCaches() {
        literalNodeCache.removeAll();
     }


}
